package test.util;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.CaseFormat;
import test.converter.CaseConverter;

import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by aceyin on 15/7/15.
 * 一个本地数据store。
 * JsonEntityStore会尝试从文件系统中加载json格式的数据文件，并保持在内存中。
 * 被加载的json数据文件格式和数据库的row是一一对应的，即：数据库中的一条数据便对应于一个json对象。
 */
public class EntityStore {
    private static final ObjectMapper mapper = new ObjectMapper();
    public static final String dataFileLocation = "/json";
    private static final String DOMAIN_PACKAGE = "domain.";
    private static final Map<String, List<Map<String, Object>>> store = new HashMap<>();

    static {
        mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
        load();
    }

    /**
     * 从data store中获取一个map，该map的参数不能等于给定的参数
     *
     * @param type
     * @param excludes
     * @return
     */
    public static Map<String, Object> nmap(Class<?> type, KV... excludes) {
        String className = getKey(type);
        if (!store.containsKey(className)) {
            return null;
        }
        if (excludes == null) {
            return anym(type);
        }

        List<Map<String, Object>> objects = store.get(className);
        try {
            for (Map<String, Object> o : objects) {
                boolean allNotMatch = false;
                for (KV kv : excludes) {
                    String key = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, kv.key);
                    if (!o.containsKey(key)) {
                        continue;
                    }
                    Object value = o.get(key);
                    if (value == null) {
                        value = "null";
                    }
                    String v = kv.value == null ? "null" : kv.value.toString();
                    if (!value.toString().equals(v.toString())) {
                        allNotMatch = true;
                    }
                }
                if (allNotMatch) {
                    return o;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 从data store中获取一个map，该map的属性符合指定的要求
     *
     * @param type
     * @param kvs
     * @return
     */
    public static Map<String, Object> map(Class<?> type, KV... kvs) {
        String className = getKey(type);
        if (!store.containsKey(className)) {
            return null;
        }
        if (kvs == null) {
            return anym(type);
        }
        List<Map<String, Object>> objects = store.get(className);
        try {
            for (Map<String, Object> o : objects) {
                boolean allMatch = true;
                for (KV kv : kvs) {
                    String key = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, kv.key);
                    if (!o.containsKey(key)) {
                        continue;
                    }
                    Object value = o.get(key);
                    if (value == null) {
                        value = "null";
                    }
                    String v = kv.value == null ? "null" : kv.value.toString();
                    if (!value.toString().equals(v.toString())) {
                        allMatch = false;
                    }
                }
                if (allMatch) {
                    return o;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * get data from store which properties not equals to specified.
     *
     * @param type
     * @param excludes
     * @param <T>
     * @return
     */
    public static <T> T not(Class<T> type, KV... excludes) {
        Map<String, Object> nmap = nmap(type, excludes);
        if (nmap == null) {
            return null;
        }
        Map<String, Object> map = CaseConverter.toCamelCase(nmap);
        T t = CaseConverter.fill(map, type);
        return t;
    }

    /**
     * Get an object with property matches given condition.
     *
     * @param type
     * @param kvs
     * @param <T>
     * @return
     */
    public static <T> T get(Class<T> type, KV... kvs) {
        Map<String, Object> map = map(type, kvs);
        if (map == null) {
            return null;
        }

        Map<String, Object> data = CaseConverter.toCamelCase(map);
        T t = CaseConverter.fill(data, type);
        return t;
    }

    /**
     * 随机获取一个clazz类型的数据
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T any(Class<T> clazz) {
        String key = getKey(clazz);
        if (!store.containsKey(key)) {
            return null;
        }
        List<Map<String, Object>> objects = store.get(key);
        Map<String, Object> m = objects.get(0);
        Map<String, Object> map = CaseConverter.toCamelCase(m);
        T t = CaseConverter.fill(map, clazz);
        return t;
    }

    /**
     * 从data store中取出符合class条件的任意一个map。
     *
     * @param clazz
     * @return
     */
    public static Map<String, Object> anym(Class<?> clazz) {
        String key = getKey(clazz);
        if (!store.containsKey(key)) {
            return null;
        }
        List<Map<String, Object>> objects = store.get(key);
        return objects.get(0);
    }

    private static <T> String getKey(Class<T> clazz) {
        return clazz.getName();
    }

    /**
     * Load data from file
     */
    public static void load() {
        URL resource = EntityStore.class.getResource(dataFileLocation);
        String path = resource.getPath();
        File directory = new File(path);
        if (directory == null || !directory.isDirectory()) {
            throw new RuntimeException("Data directory is empty");
        }
        String[] list = directory.list((dir, name) -> name.endsWith(".json"));
        for (String dir : list) {
            String name = dir.substring(0, dir.indexOf(".json"));
            String className = DOMAIN_PACKAGE + CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name);
            try {
                JavaType javaType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, Map.class);
                String content = readString(path + File.separator + dir);

                ArrayList<Map<String, Object>> values = mapper.readValue(content, javaType);

                if (values != null) {
                    store.put(className, values);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private static String readString(String file) throws IOException {
        StringBuilder sb = new StringBuilder();
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(file))));
        try {
            String s;
            while ((s = br.readLine()) != null) {
                sb.append(s);
            }
            return sb.toString();
        } finally {
            if (br != null) {
                br.close();
            }
        }
    }
}
